#include "COffscreenSurface.h"

#include <QScreen>



void COffscreenSurface::initializeInternal(){
    if(!_initialized.exchange(true))
    {
        _context=std::make_shared<QOpenGLContext>(this);
        _context->setFormat(format());
        if(_context->create())
        {
            _context->makeCurrent(this);
            this->initializeOpenGLFunctions();
            recreateFboAndDevice();
        }
        else
        {
            _initialized=false;
            _context.reset();
            throw ("failed to create opengl context");
        }
    }
}

void COffscreenSurface::bindFramebufferObject(){
    if(_fbo)
    {
        _fbo->bind();
    }
    else
    {
        QOpenGLFramebufferObject::bindDefault();
    }
}

void COffscreenSurface::makeCurrentInternal(){
    if(isValid())
    {
        _context->makeCurrent(this);
    }
    else {
        throw("COffscreenSurface::makeCurrent-Window not yet properly initialized!");
    }
}

void COffscreenSurface::doneCurrent(){
    if(_context)
    {
        _context->doneCurrent();
    }
}

QImage COffscreenSurface::grabFramebuffer(){
    makeCurrentInternal();
    if(_fbo->format().samples()>0)
    {
        glBindFramebuffer(GL_FRAMEBUFFER,_resolvingFbo->handle());
        glBindFramebuffer(GL_FRAMEBUFFER,_fbo->handle());
        if(GLenum err=glGetError()!=GL_NO_ERROR)
        {
            qDebug()<<"COffscreenSurface::grabFramebuffer() - opengl error"<<err;
        }
        return grabFramebufferInternal(_resolvingFbo.get());
    }
    else {
        return grabFramebufferInternal(_fbo.get());
    }
}

QImage COffscreenSurface::grabFramebufferInternal(QOpenGLFramebufferObject *fbo)
{
    QImage image;
    glBindFramebuffer(GL_READ_FRAMEBUFFER,_fbo->handle());
    GLenum internalFormat=_fbo->format().internalTextureFormat();
    bool hasAlpha=internalFormat==GL_RGBA||internalFormat==GL_BGRA||
            internalFormat==GL_RGBA8;
    if(internalFormat==GL_BGRA)
    {
        image=QImage(fbo->size(),hasAlpha?QImage::Format_ARGB32:QImage::Format_RGB32);
        glReadPixels(0,0,_fbo->size().width(),
                     _fbo->size().height(),GL_BGRA,GL_UNSIGNED_BYTE,image.bits());
    }
    else if((internalFormat==GL_RGBA)||(internalFormat==GL_RGBA8)){
        image=QImage(_fbo->size(),hasAlpha?QImage::Format_RGBA8888:QImage::Format_RGBX8888);
        glReadPixels(0,0,_fbo->size().width(),
                     _fbo->size().height(),GL_RGBA,GL_UNSIGNED_BYTE,image.bits());
    }
    else {
        qDebug()<<"COffscreenSurface::grabFramebuffer() - Unsupported framebuffer format"<<internalFormat;
    }
    glBindFramebuffer(GL_FRAMEBUFFER,_fbo->handle());
    return image.mirrored();
}

void COffscreenSurface::swapBuffersInternal(){
    _context->makeCurrent(this);
    glFlush();
    glBindFramebuffer(GL_FRAMEBUFFER,0);
    glBindFramebuffer(GL_FRAMEBUFFER,_fbo->handle());
    if(GLenum err=glGetError()!=GL_NO_ERROR)
    {
        qDebug()<<"COffscreenSurface::swapBufferInternal() - error "<<err;
    }
    _context->swapBuffers(this);
}

void COffscreenSurface::recreateFboAndDevice(){
    if(_context&&((!_fbo)||_fbo->size()!=_size)){
        _context->makeCurrent(this);
        if(_fbo)
        {
            _fbo->release();
            _fbo.reset();
        }
        if(_resolvingFbo)
        {
            _resolvingFbo->release();
            _resolvingFbo.reset();
        }
        QOpenGLFramebufferObjectFormat format;
        format.setSamples(0);
        _fbo=std::make_shared<QOpenGLFramebufferObject>(_size,format);
        if(!_fbo->isValid())
        {
            throw("COffscreenSurface:recreateFbo - failed");
        }
        _fbo->bind();
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
        _fbo->release();
        if(format.samples()>0){
            format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
            format.setSamples(0);
            _resolvingFbo=std::make_shared<QOpenGLFramebufferObject>(_size,format);
            if(!_resolvingFbo->isValid())
                throw("COffscreenSurface::recreateFbo() - failed to create resolving fbo");
            _resolvingFbo->bind();
            glClear(GL_COLOR_BUFFER_BIT);
            _resolvingFbo->release();
        }
    }
    if(!_device)
    {
        _device=std::make_shared<QOpenGLPaintDevice>();
    }
    if(_device->size()!=_size)
        _device->setSize(_size);

}

void COffscreenSurface::render(){
    initializeInternal();
    if(!_initializedGL)
    {
        _initializedGL=true;
        initializeGL();
    }
    makeCurrent();
    bindFramebufferObject();
    paintGL();
    doneCurrent();

}

void COffscreenSurface::resize(const QSize &nsize)
{
    makeCurrent();
    _size=nsize;
    recreateFboAndDevice();
    resizeGL(_size.width(),_size.height());
    doneCurrent();
}

bool COffscreenSurface::event(QEvent *e)
{
    switch (e->type()) {
    case QEvent::UpdateLater:
        update();
        return true;
    case QEvent::UpdateRequest:
        render();
        return true;
    default:return false;
    }
}

COffscreenSurface::~COffscreenSurface()
{
    _context->makeCurrent(this);
    if(_fbo)
    {
        _fbo->release();
        _fbo.reset();
    }
    if(_resolvingFbo)
    {
        _resolvingFbo->release();
        _resolvingFbo.reset();
    }
    _shader.reset();
    _context->doneCurrent();
    _context.reset();
    _device.reset();
    _initialized=false;
    destroy();
}
